package cmp.dataholders;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import cmp.main.Globals;

import util.Debug.ErrorReport;

public class MultiAlignmentData {
	
	public MultiAlignmentData(String projectName) {
		strProjectName = projectName;
		
		sequenceNames = new Vector<String> ();
		sequences = new Vector<String> ();
	}
	
	public boolean hasFilename() { return strPOGName != null; }
	public void setFilename(String filename) { strPOGName = filename; }
	public String getFilename() { return strPOGName; }
	
	public void addSequence(String name, String AASequence) {
		sequenceNames.add(name);
		sequences.add(AASequence);
	}
	
	public void alignSequences() {
		try {
			writeFASTA(Globals.MultiAlign.DEFAULT_SOURCE_FILE_NAME);
			String cmd = Globals.MultiAlign.MUSCLE_PATH + " -in " + Globals.MultiAlign.DEFAULT_SOURCE_FILE_NAME 
					+ " -out " + Globals.MultiAlign.DEFAULT_TARGET_FILE_NAME;
			System.out.println(cmd);
			Process pr = Runtime.getRuntime().exec(cmd);
			pr.waitFor();
			readFASTA(Globals.MultiAlign.DEFAULT_TARGET_FILE_NAME, true, true);
			// CAS 2/24 - export doesn't work (use to work) and its hardcoded to 'rhi'
			// so removing the "Export" feature for this release, and just removing these.
			// deleteFile(Globals.MultiAlign.DEFAULT_SOURCE_FILE_NAME);
			// deleteFile(Globals.MultiAlign.DEFAULT_TARGET_FILE_NAME);
		} catch(Exception e) {
			ErrorReport.reportError(e);
		}
	}
	
	public void writeMuscleCache() {
		String targetDirectory = Globals.PROJECTDIR + "/" + strProjectName + "/" + Globals.MultiAlign.DEFAULT_TARGET_DIRECTORY + "/";
		System.err.println("Write to: " + targetDirectory);
		File targetDir = new File(targetDirectory);
		if(!targetDir.exists())
			targetDir.mkdir();
		writeFASTA(targetDirectory + strPOGName);
	}
	
	public boolean readMuscleCache() {
		String strSource = Globals.PROJECTDIR + "/" + strProjectName + "/" + Globals.MultiAlign.DEFAULT_TARGET_DIRECTORY + "/" + strPOGName;
		File source = new File(strSource);
		if(!source.exists())
			return false;
		readFASTA(strSource, true, false);
		return true;
	}
	
	private void writeFASTA(String name) {
		try {
			PrintWriter out = new PrintWriter(new FileWriter(name));
			
			Iterator<String> nameIter = sequenceNames.iterator();
			Iterator<String> seqIter = sequences.iterator();
			
			while(nameIter.hasNext()) {
				out.println(">" + nameIter.next());
				int position = 0;
				String theSequence = seqIter.next();
				for(;(position + Globals.MultiAlign.SEQUENCE_LINE_LENGTH) < theSequence.length(); position += Globals.MultiAlign.SEQUENCE_LINE_LENGTH) {
					out.println(theSequence.substring(position, position+Globals.MultiAlign.SEQUENCE_LINE_LENGTH));
				}
				out.println(theSequence.substring(position));
			}
			
			out.close();
		} catch(Exception e) {
			ErrorReport.reportError(e);
		}
	}
	
	private void deleteFile(String fileName) {
		try {
			File f = new File(fileName);
			if(!f.delete()) throw new Exception();
		} catch(Exception e) {
			ErrorReport.reportError(e);
		}
	}
	
	private void readFASTA(String name, boolean replaceSequences, boolean createConsensus) {
		try {
			BufferedReader in = new BufferedReader(new FileReader(name));
			
			if(replaceSequences) {
				sequenceNames.clear();
				sequences.clear();
			}
			
			String theSequence = "", theSequenceName = "", line;
			while((line = in.readLine()) != null) {
				if(line.startsWith(">")) {
					if(theSequenceName.length() > 0 && theSequence.length() > 0) {
						sequenceNames.add(theSequenceName);
						sequences.add(theSequence.replace('-', '.'));
					}
					theSequenceName =  line.substring(1);
					theSequence = "";
				} else {
					theSequence += line;
				}
			}
			if(theSequenceName.length() > 0 && theSequence.length() > 0) {
				sequenceNames.add(theSequenceName);
				sequences.add(theSequence.replace('-', '.'));
			}
			
			//If wanted, create consensus at position 0
			if(createConsensus)
				setConsensus();
			
			in.close();			
		} catch(Exception e) {
			ErrorReport.reportError(e);
		}
	}
	
	private void setConsensus() {
		String theConsensus = "";
		
		int nSeqLength = sequences.get(0).length(); //All sequences are aligned.. they are all the same length
		
		//Get counts of each symbol
		Hashtable<Character, Integer> counts = new Hashtable<Character, Integer>();
		for(int x=0; x<nSeqLength; x++) {
			counts.clear();
			Iterator<String> seqIter = sequences.iterator();
			while(seqIter.hasNext()) {
				char c= seqIter.next().charAt(x);
				if(counts.containsKey(c))
					counts.put(c, counts.get(c) + 1);
				else
					counts.put(c, 1);
			}

			//Convert counts to a list
			Vector<SymbolCount> theCountList = new Vector<SymbolCount> ();
			for(Map.Entry<Character, Integer> val:counts.entrySet())
				theCountList.add(new SymbolCount(val.getKey(), val.getValue()));
		
			//Map no longer needed at this point
			counts.clear();
			Collections.sort(theCountList);
			
			//Test if the there is one symbol most frequent
			if( getTotalCount(theCountList) <= 1)
				theConsensus += '.';
			else if( theCountList.size() == 1 || (theCountList.get(0).theCount > theCountList.get(1).theCount && theCountList.get(0).theSymbol != '.'))
				theConsensus += theCountList.get(0).theSymbol;
			else if ( theCountList.get(0).theSymbol == '.' && (theCountList.size() == 2 || theCountList.get(1).theCount > theCountList.get(2).theCount))
				theConsensus += theCountList.get(1).theSymbol;
			else
				theConsensus += getMostCommonRelated(theCountList);
		}
		sequenceNames.insertElementAt("Consensus", 0);
		sequences.insertElementAt(theConsensus, 0);
	}
	
	private static Character getMostCommonRelated(Vector<SymbolCount> theSymbols) {
		//Special case: need at least 2 non-gap values to have consensus
		if(getTotalCount(theSymbols) == 1)
			return '.';
		
		//Create/initialize counters
		int [] relateCounts = new int[theSymbols.size()];
		for(int x=0; x<relateCounts.length; x++)
			relateCounts[x] = 0;
		
		//Going with the assumption that relationships are not mutually inclusive
		for(int x=0; x<relateCounts.length; x++) {
			for(int y=x+1; y<relateCounts.length; y++) {
				if(AAStatistics.isCommonAcidSub(theSymbols.get(x).theSymbol, theSymbols.get(y).theSymbol)) {
					relateCounts[x]++;
					relateCounts[y]++;
				}
			}
		}

		//Find highest value
		int maxPos = 0;
		
		for(int x=1; x<relateCounts.length; x++) {
			if( (relateCounts[x]) > (relateCounts[maxPos]) )
				maxPos = x;
		}

		return theSymbols.get(maxPos).theSymbol;
	}

	private static int getTotalCount(Vector<SymbolCount> theCounts) {
		int retVal = 0;
		
		Iterator<SymbolCount> iter = theCounts.iterator();
		while(iter.hasNext()) {
			SymbolCount temp = iter.next();
			if(temp.theSymbol != '.')
				retVal += temp.theCount;
		}
		
		return retVal;
	}

	//Data structure for sorting/retrieving counts
	private class SymbolCount implements Comparable<SymbolCount> {
		public SymbolCount(Character symbol, Integer count) {
			theSymbol = symbol;
			theCount = count;
		}
		
		public int compareTo(SymbolCount arg) {
			return -1 * theCount.compareTo(arg.theCount);
		}
		
		public Character theSymbol;
		public Integer theCount;
	}
	
	public int getNumSequences() { return sequences.size(); }
	
	public String [] getSequenceNames() { return sequenceNames.toArray(new String[sequences.size()]); }
	public String [] getSequences() { return sequences.toArray(new String[sequences.size()]); }
	
	private Vector<String> sequenceNames = null;
	private Vector<String> sequences = null;
	
	private String strProjectName = null;
	private String strPOGName = null;
}
